package weChatPay

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/gocms/common/oredis"
	"gitee.com/gocms/common/util"
	"gitee.com/gocms/opay/utils"
	"gitee.com/gocms/opay/weChatPay/option"
	"io/ioutil"
	"net/url"
	"strings"
	"time"
)

var (
	Wx = WxToken{
		MqAppID:                 "",
		MqAppSecret:             "",
		URL:                     "",
		Host:                    "",
		EncodingAESKey:          "",
		EncodingAESType:         0,
		Token:                   "",
		MiniAppId:               "",
		MiniAppSecret:           "",
		MchID:                   "",
		ApiV3Key:                "",
		CertificateSerialNumber: "",
		PrivateKey:              ``,
		WeChatCertificateStr:    ``,

		OpenAppid:     "",
		OpenAppSecret: "",
	}
)

type WxMqEventData struct {
	Id           string `json:"id" gorm:"primary_key;size:33"` //主键ID
	Event        string `gorm:"size:33"`                       //事件类型
	MsgType      string `gorm:"size:15"`                       //消息类型，文本为 text
	ToUserName   string `gorm:"size:33"`                       //开发者微信号
	FromUserName string `gorm:"size:33"`                       //发送方帐号（一个OpenID）
	Data         string `json:"data"`                          //消息主体（一个OpenID）
	CreateTime   int64  // 创建时间
} // 微信事件//`fv_wx_mq_event_data`

type WxMqMsgData struct {
	Id           string     `json:"id" gorm:"primary_key;size:33"` //主键ID
	MsgId        string     `gorm:"size:33"`                       //消息id，64位整型
	MsgType      string     `gorm:"size:15"`                       //消息类型，文本为 text
	ToUserName   string     `gorm:"size:33"`                       //开发者微信号
	FromUserName string     `gorm:"size:33"`                       //发送方帐号（一个OpenID）
	Data         string     //消息主体（一个OpenID）
	CreateTime   int64      // 创建时间
	CreateDate   *time.Time `json:"create_date" gorm:"type:datetime"` // 创建时间
} // 用户消息主体//`fv_wx_mq_msg_data`

//微信

type WxPayNotify struct {
	TransactionId string     `json:"transaction_id" gorm:"primary_key;size:33"` //微信支付订单号
	OutTradeNo    string     `json:"out_trade_no" gorm:"size:33"`               //商户订单号
	Appid         string     `json:"appid" gorm:"size:68"`                      //交易类型
	MchId         string     `json:"mch_id" gorm:"size:20"`                     //交易类型
	Openid        string     `json:"openid" gorm:"size:68"`                     //交易类型
	TradeType     string     `json:"trade_type" gorm:"size:10"`                 //交易类型
	ResultCode    string     `json:"result_code" gorm:"size:10"`                //交易类型
	ReturnCode    string     `json:"return_code" gorm:"size:10"`                //交易类型
	ErrCode       string     `json:"err_code" gorm:"size:68"`                   //交易类型
	ErrCodeDes    string     `json:"err_code_des" gorm:"size:300"`              //错误返回的信息描述
	TotalFee      string     `json:"total_fee"  gorm:"type:int"`                //错误返回的信息描述
	Body          string     `json:"body" `
	CreateTime    *time.Time `json:"createTime" gorm:"type:datetime"` // 创建时间
} //微信支付回调//`fv_wx_pay_notify`
type WxUserInfo struct {
	Id         string     `json:"id" gorm:"primaryKey;size:10"`
	Openid     string     `json:"openid" gorm:"size:33;index"`             //用户的唯一标识
	AppOpenid  string     `json:"app_openid" gorm:"size:33;index"`         //用户的唯一标识
	Unionid    string     `json:"unionid" gorm:"size:33;index"`            //只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段。
	Nickname   string     `json:"nickname" gorm:"size:100"`                //用户昵称
	Uid        string     `json:"uid" gorm:"size:10;index"`                // 用户ID
	Sex        int        `json:"sex" gorm:"type:tinyint(2);DEFAULT:0"`    //用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
	Province   string     `json:"province" gorm:"size:50"`                 //用户个人资料填写的省份
	City       string     `json:"city" gorm:"size:50"`                     //普通用户个人资料填写的城市
	Country    string     `json:"country" gorm:"size:50"`                  //国家，如中国为CN
	HeadImgUrl string     `json:"head_img_url" gorm:"size:300"`            //用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像URL将失效。
	Status     int        `json:"status" gorm:"type:tinyint(2);DEFAULT:0"` //状态
	CreateTime *time.Time `json:"create_time" gorm:"type:datetime"`        // 创建时间
} // 微信用户信息//`fv_wx_user_info`
type WxToken struct {
	MqAppID         string `json:"mq_app_id" gorm:"primary_key;size:33"`     //MqAppID
	MqAppSecret     string `json:"mq_app_secret" gorm:"uniqueIndex;size:33"` //MqAppSecret
	URL             string `json:"URL" gorm:"size:300"`                      //必须以http://或https://开头，分别支持80端口和443端口。
	Host            string `json:"host" gorm:"size:300"`                     //必须以http://或https://开头，分别支持80端口和443端口。
	EncodingAESKey  string `json:"encoding_aes_key" gorm:"size:43"`          //消息加密密钥由43位字符组成，可随机修改，字符范围为A-Z，a-z，0-9。
	EncodingAESType int    `json:"encoding_aes_type" gorm:"DEFAULT:0"`       // 加密方式：0、明文 1、兼容模式 2、安全模式
	Token           string `json:"token" gorm:"size:43"`                     //必须为英文或数字，长度为3-32字符。
	RsaPrivateKey   *rsa.PrivateKey
	X509Certificate *x509.Certificate
	OpenAppid       string `json:"open_appid"`      //微信开发平台
	OpenAppSecret   string `json:"open_app_secret"` //微信开发平台秘钥

	MiniAppId     string `json:"mini_app_id"`
	MiniAppSecret string `json:"mini_app_secret"`

	MchID                   string `json:"mch_id"`                     //商户ID
	ApiV3Key                string `json:"api_v3_key"`                 //商户ID
	CertificateSerialNumber string `json:"certificate_serial_number"`  // 证书序列号
	PrivateKey              string `json:"private_key"`                // 私钥
	WeChatCertSerialNumber  string `json:"we_chat_cert_serial_number"` //证书序列号
	WeChatCertificateStr    string `json:"we_chat_certificate_str"`    //公钥

	CreateTime *time.Time `json:"create_time"` // 创建时间
}

func (w *WxToken) GetLoadPrivateKey() (c *rsa.PrivateKey, err error) {
	c, err = utils.LoadPrivateKey(w.PrivateKey)
	if err != nil {
		return
	}
	w.RsaPrivateKey = c
	return c, err
}
func (w *WxToken) LoadCertificate() (c *x509.Certificate, err error) {
	c, err = utils.LoadCertificate(w.PrivateKey)
	if err != nil {
		return
	}
	w.X509Certificate = c
	return c, err
}
func (w *WxToken) GetLoadCertificate() (*x509.Certificate, error) {
	Certificate, err := w.getCertificate(nil)
	if err != nil {
		return nil, err
	}
	return utils.LoadCertificate(Certificate)
}
func (w *WxToken) Verification() interface{} {
	// 判断防伪码生成规则
	m := make(map[string]string)
	ms := make([]string, 0)
	_, err := url.Parse(w.URL)
	if err != nil || len(w.URL) > 300 {
		m["URL"] = "URL不合格，长度不能超过300"
		ms = append(ms, m["URL"])
	}

	if !util.CheckRegexp(w.MqAppID, `^[A-Za-z0-9]{16,20}$`) {
		m["MqAppID"] = "MqAppID必须为英文或数字，长度为16-20字符"
		ms = append(ms, m["mqAppID"])
	}
	if !util.CheckRegexp(w.MqAppSecret, `^[A-Za-z0-9]{32}$`) {
		m["mqAppSecret"] = "MqAppSecret必须为英文或数字，长度为32字符"
		ms = append(ms, m["mqAppSecret"])
	}
	if !util.CheckRegexp(w.Token, `^[A-Za-z0-9]{3,32}$`) {
		m["token"] = "token必须为英文或数字，长度为3-32字符"
		ms = append(ms, m["token"])
	}

	if w.EncodingAESType > 2 {
		m["encodingAESType"] = "加密方式错误"
		ms = append(ms, m["encodingAESType"])
	}

	if !util.CheckRegexp(w.EncodingAESKey, `^[A-Za-z0-9]{43}$`) {
		m["encodingAESKey"] = "消息加密密钥由43位字符组成，可随机修改，字符范围为A-Z，a-z，0-9。"
		ms = append(ms, m["encodingAESKey"])
	}

	if len(ms) > 0 {
		//return [3]interface{}{m, ms, strings.Join(ms, "\n")}
		return strings.Join(ms, "\n")
	}
	return nil
}
func (w *WxToken) GetMqAccessToken() {
	//v,err := oredis.Ser.Get("wx:mq:"+w.Id+":accessToken")
	//if err {
	//
	//}
}

// 获取新的 并保存
func (w *WxToken) getCertificate(saveCertSerialNumber func(w *WxToken)) (gotCertificate string, err error) {
	ctx := context.Background()
	privateKey, err := utils.LoadPrivateKey(w.PrivateKey)
	if err != nil {
		panic(fmt.Errorf("load private err:%s", err.Error()))
	}
	weChatPayCertificate, err := utils.LoadCertificate(w.WeChatCertificateStr)
	if err != nil {
		panic(fmt.Errorf("load certificate err:%s", err.Error()))
	}

	opts := []option.ClientOption{
		option.WithMerchant(w.MchID, w.CertificateSerialNumber, privateKey),
		option.WithWechatPay([]*x509.Certificate{weChatPayCertificate}),
	}
	client, err := NewClient(ctx, opts...)
	response, err := client.Get(ctx, GetCertificatesURL)
	body, err := ioutil.ReadAll(response.Body)
	var resp CertificatesResp
	json.Unmarshal(body, &resp)
	cer := resp.Data[0]
	ec := cer.EncryptCertificate
	gotCertificate, err = utils.DecryptToString(w.ApiV3Key, ec.AssociatedData, ec.Nonce, ec.Ciphertext)
	if err != nil {
		return
	}
	w.WeChatCertificateStr = gotCertificate
	w.WeChatCertSerialNumber = cer.SerialNo
	saveCertSerialNumber(w)
	return
} // 获取支付证书证书

type WxMqUserInfo struct {
	WxUserInfo
} // 微信用户信息
type WxPcUserInfo struct {
	WxUserInfo
} // 微信用户信息

type WxTokenVo struct {
	WxToken
	Echostr   string `json:"echostr"`
	Nonce     string `json:"nonce"`
	Signature string `json:"signature"`
	Timestamp string `json:"timestamp"`
}

var (
	GetCertificatesURL = "https://api.mch.weixin.qq.com/v3/certificates"            //获取商户平台证书
	WeChatPayV3Refunds = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds" // 申请退款
	WeChatPayJsApi     = `https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi`  // 申请退款
)

/*
获取access_token
接口调用请求说明
https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数		是否必须		说明
grant_type	是			获取access_token填写client_credential
appid		是			第三方用户唯一凭证
secret		是			第三方用户唯一凭证密钥，即appsecret
*/

type WxAccessToken struct {
	AccessToken  string `json:"access_token"`  //获取到的凭证
	Ticket       string `json:"ticket"`        //获取到的凭证
	ExpiresIn    int    `json:"expires_in"`    //凭证有效时间，单位：秒
	Errcode      int    `json:"errcode"`       //返回码
	Errmsg       string `json:"errmsg"`        //说明
	RefreshToken string `json:"refresh_token"` //填写通过access_token获取到的refresh_token参数
	Openid       string `json:"openid"`        //用户的唯一标识
	Unionid      string `json:"unionid"`       //用户的唯一标识
	Scope        string `json:"scope"`         //说明
}

type WxMqMsg struct {
	MsgId        string //消息id，64位整型
	MsgType      string //消息类型，文本为 text
	ToUserName   string //开发者微信号
	FromUserName string //发送方帐号（一个OpenID）
	CreateTime   int64  //消息创建时间 （整型）
}

type WxMqMsgAll struct {
	WxMqMsg
	Content      string //文本消息内容
	MediaId      string //图片消息媒体id，可以调用获取临时素材接口拉取数据。
	PicUrl       string //图片链接（由系统生成）
	Format       string //语音格式，如amr，speex等
	Recognition  string //语音识别结果，UTF8编码
	ThumbMediaId string //图片链接（由系统生成）f
	Location_X   string //图片链接（由系统生成）
	Location_Y   string //图片链接（由系统生成）
	Scale        string //图片链接（由系统生成）
	Label        string //图片链接（由系统生成）
	Title        string //消息标题
	Description  string //消息描述
	Url          string //消息链接
}

// 文本消息 text
type WxMqMsgText struct {
	WxMqMsg
	Content string //文本消息内容
}
type WxMqMsgMedia struct {
	WxMqMsg
	MediaId string //图片消息媒体id，可以调用获取临时素材接口拉取数据。
}

// 图片消息 image
type WxMqMsgMediaImg struct {
	WxMqMsgMedia
	PicUrl string //图片链接（由系统生成）
}

// 语音消息 voice
type WxMqMsgMediaVoice struct {
	WxMqMsgMedia
	Format      string //语音格式，如amr，speex等
	Recognition string //语音识别结果，UTF8编码
}

// 视频消息 video / shortvideo 小视频消息
type WxMqMsgMediaVideo struct {
	WxMqMsgMedia
	ThumbMediaId string //图片链接（由系统生成）
}

//地理位置消息 location
type WxMqMsgLocation struct {
	WxMqMsg
	Location_X string //地理位置维度
	Location_Y string //地理位置经度
	Scale      string //地图缩放大小
	Label      string //地理位置信息
}

//链接消息 link
type WxMqMsgLink struct {
	WxMqMsg
	Title       string //消息标题
	Description string //消息描述
	Url         string //消息链接
}

type WxMqEvent struct {
	MsgType      string //消息类型，文本为 text
	ToUserName   string //开发者微信号
	FromUserName string //发送方帐号（一个OpenID）
	CreateTime   int64  //消息创建时间 （整型）
}
type WxMqEventAll struct {
	MsgType      string //消息类型，文本为 text
	ToUserName   string //开发者微信号
	FromUserName string //发送方帐号（一个OpenID）
	CreateTime   int64  //消息创建时间 （整型）
	Event        string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	EventKey     string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Ticket       string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Latitude     string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Longitude    string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Precision    string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
}

// 关注取消关注事件
// 自定义菜单事件\点击菜单跳转链接时的事件推送
type WxMqEventSub struct {
	WxMqEvent
	Event    string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	EventKey string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
}

// 关注
type WxMqEventSubscribe struct {
	WxMqEventSub
	Ticket string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
}

// 上报地理位置事件
type WxMqEventLatitude struct {
	WxMqEventSub
	Latitude  string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Longitude string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
	Precision string //事件类型，subscribe(订阅)、unsubscribe(取消订阅)
}

var SubscribeScene = map[string]string{
	"ADD_SCENE_SEARCH":            "公众号搜索",
	"ADD_SCENE_ACCOUNT_MIGRATION": "公众号迁移",
	"ADD_SCENE_PROFILE_CARD":      "名片分享",
	"ADD_SCENE_QR_CODE":           "扫描二维码",
	"ADD_SCENE_PROFILE_ LINK":     "图文页内名称点击",
	"ADD_SCENE_PROFILE_ITEM":      "图文页右上角菜单",
	"ADD_SCENE_PAID":              "支付后关注",
	"ADD_SCENE_OTHERS":            "其他",
}

func (v *WxMqUserInfo) To() WxMqUserInfoVo {
	return WxMqUserInfoVo{
		//Uid: v.Uid,
		Unionid:    v.Unionid,
		Openid:     v.Openid,
		Nickname:   v.Nickname,
		Sex:        v.Sex,
		Province:   v.Province,
		City:       v.City,
		Country:    v.Country,
		HeadImgUrl: v.HeadImgUrl,
		//Privilege:  strings.Split(v.Privilege, ","),
	}
}

type WxMqUserInfoVo struct {
	Uid        string   `json:"uid"`        //主键ID
	Unionid    string   `json:"unionid"`    //只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段。
	Openid     string   `json:"openid"`     //用户的唯一标识
	Nickname   string   `json:"nickname"`   //用户昵称
	Sex        int      `json:"sex"`        //用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
	Province   string   `json:"province"`   //用户个人资料填写的省份
	City       string   `json:"city"`       //普通用户个人资料填写的城市
	Country    string   `json:"country"`    //国家，如中国为CN
	HeadImgUrl string   `json:"headimgurl"` //用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像URL将失效。
	Privilege  []string `json:"privilege"`  //用户特权信息，json 数组，如微信沃卡用户为（chinaunicom）
}

func (v *WxMqUserInfoVo) To() WxMqUserInfo {
	return WxMqUserInfo{WxUserInfo{
		//Uid: v.Uid,
		Unionid:    v.Unionid,
		Openid:     v.Openid,
		Nickname:   v.Nickname,
		Sex:        v.Sex,
		Province:   v.Province,
		City:       v.City,
		Country:    v.Country,
		HeadImgUrl: v.HeadImgUrl,
		//Privilege:  strings.Join(v.Privilege, ",")
	},
	}
}

var (
	//URLHost = "http://dwx.free.idcfengye.com/"
	//  若提示“该链接无法访问”，请检查参数是否填写错误，是否拥有scope参数对应的授权作用域权限。
	URLWxMqOAuth2Authorize       = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&state=%s&scope=%s#wechat_redirect"
	URLWxCgiBinToken             = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
	URLWxCgiBinUserInfo          = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN`
	CgiBinMenuCreate             = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s`               // 菜单创建接口
	CgiBinGetCurrentSelfMenuInfo = `https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=%s` // 菜单查询接口
	CgiBinMenuDelete             = `https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s`               // 菜单删除接口
	URLWxMqMessageTemplateSend   = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s`
	URLWxJsApiGetTicket          = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi`
	URLWxConnectArconnect        = `https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect`
	URLWxSnsOauth2RefreshToken   = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"
	URLWxSnsOauth2AccessToken    = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
	URLWxSnsUserInfo             = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN" //http：GET（请使用https协议）

	jscode2session = `https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code`
)

/*
func Event(v model.WxMqMsgAnd) {
	switch v.Event {
	case "subscribe": // 关注
		break
	case "unsubscribe": // 取消关注
		break
	case "LOCATION": // 地理位置
		break
	case "CLICK": // 自定义菜单事件
		break
	case "VIEW": // 点击菜单跳转链接时的事件推送
		break
	}
}*/
func (w *WxToken) GetAccessTokenStr() (token string, err error) {
	a, err := w.GetAccessTokenNew()
	if err != nil {
		return
	}
	token = a.AccessToken
	return
}

func (wx *WxToken) GetClient() (client *Client, err error) {
	c, err := wx.GetLoadPrivateKey()
	if err != nil {
		return
	}
	w, err := wx.GetLoadCertificate()
	if err != nil {
		return
	}
	opts := []option.ClientOption{
		option.WithMerchant(wx.MchID, wx.CertificateSerialNumber, c),
		option.WithWechatPay([]*x509.Certificate{w}),
	}
	return NewClient(context.Background(), opts...)
}

type MiniJsCode2Session struct {
	Openid     string `json:"openid"`      // 用户唯一标识
	SessionKey string `json:"session_key"` // 会话密钥
	Unionid    string `json:"unionid"`     //用户在开放平台的唯一标识符，在满足 UnionID 下发条件的情况下会返回，详见 UnionID 机制说明。
	ErrCode    int    `json:"errcode"`     //错误码
	ErrMsg     string `json:"errmsg"`      //错误信息
}

type MiniJsCode2SessionLogin struct {
	MiniJsCode2Session `json:"-"`
	Token              string `json:"token"`
	Type               int    `json:"type"`
}

func (w *WxToken) JsCode2Session(c string) (r string, token MiniJsCode2Session, err error) {
	h := util.HttpOk{Url: fmt.Sprintf(jscode2session, w.MiniAppId, w.MiniAppSecret, c)}
	err = h.Get()
	if err != nil {
		return
	}
	r = h.ResponseBody
	util.JsonToType(h.ResponseBody, &token)
	if token.ErrCode != 0 {
		err = ErrorsNew(token.ErrMsg)
	}
	return
} //登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 小程序登录。

func (w *WxToken) GetJsApiGetTicket() (token string, err error) {
	accessToken, err := w.GetJsApiGetTicketNew()
	if err != nil {
		return
	}
	return accessToken.Ticket, nil
}
func (w *WxToken) GetAccessTokenNew() (token WxAccessToken, err error) {
	http := util.HttpOk{Url: fmt.Sprintf(URLWxCgiBinToken, w.MqAppID, w.MqAppSecret)}
	http.Query()
	v := http.ResponseBody
	util.JsonToType(v, &token)
	if len(token.Errmsg) > 2 {
		err = ErrorsNew(v)
		return
	}
	return token, nil
}
func (w *WxToken) SetAccessToken(key string) (token WxAccessToken, err error) {
	return w.GetAccessTokenNew()
}

func (w *WxToken) SetJsApiGetTicket(key string) (token string, err error) {
	accessToken, err := w.GetJsApiGetTicketNew()
	if err != nil {
		return
	}
	err = oredis.Ser.SetType(key, accessToken, 110*time.Minute)
	if err != nil {
		return
	}
	return accessToken.Ticket, nil
}
func (w *WxToken) GetJsApiGetTicketNew() (t WxAccessToken, err error) {
	token, err := w.GetAccessTokenStr()
	if err != nil {
		return
	}
	URL := strings.ReplaceAll(URLWxJsApiGetTicket, "ACCESS_TOKEN", token)
	http := util.HttpOk{Url: URL}
	http.QueryGet()
	v := http.ResponseBody
	util.JsonToType(v, &t)
	if len(t.Errmsg) > 2 {
		err = ErrorsNew(v)
		return
	}
	//oredis.Ser.SetKV(,"")
	return t, nil
}

func (w *WxToken) MenuCreate(menu interface{}) (str string, err error) {
	token, err := w.GetAccessTokenStr()
	if err != nil {
		return
	}
	http := util.HttpOk{Url: fmt.Sprintf(CgiBinMenuCreate, token), Method: "POST"}
	http.Params = menu
	http.Query()
	v := http.ResponseBody
	return v, nil
}

func (w *WxToken) MenuInfo() (str string, err error) {
	ACCESS_TOKEN, err := w.GetAccessTokenStr()
	if err != nil {
		return
	}
	http := util.HttpOk{Url: fmt.Sprintf(CgiBinGetCurrentSelfMenuInfo, ACCESS_TOKEN), Method: "GET"}
	http.Query()
	v := http.ResponseBody
	return v, nil
}
func (w *WxToken) MenuDelete() (str string, err error) {
	ACCESS_TOKEN, err := w.GetAccessTokenStr()
	if err != nil {
		return
	}
	http := util.HttpOk{Url: fmt.Sprintf(CgiBinMenuDelete, ACCESS_TOKEN), Method: "GET"}
	http.Query()
	v := http.ResponseBody
	return v, nil
}

// TODO 网页用户授权

// 第一步：用户同意授权，获取code
func (w *WxToken) GetURLWxMqOAuth2Authorize(state string, REDIRECT_URI string) string {
	return fmt.Sprintf(URLWxMqOAuth2Authorize, w.MqAppID, url.QueryEscape(REDIRECT_URI), state)
}

// 静默方式登录
func (w *WxToken) GetURLWxMqOAuth2AuthorizeBase(state, scope string) string {
	return fmt.Sprintf(URLWxMqOAuth2Authorize, w.MqAppID, url.QueryEscape(w.Host+"/wx/authorize?scope="+scope), state, scope)
}
func (w *WxToken) GetURLWxConnectArconnect(redirect_uri, state string) string {
	return fmt.Sprintf(URLWxConnectArconnect, w.MqAppID, url.QueryEscape(redirect_uri), state)
}

//第二步：通过code换取网页授权access_token
func WxSnsOAuth2AccessToken(appID, appSecret, code string) (at WxAccessToken, err error) {
	h := util.HttpOk{Url: fmt.Sprintf(URLWxSnsOauth2AccessToken, appID, appSecret, code)}
	h.Query()
	resp := h.ResponseBody
	if strings.Index(resp, "errcode") > 0 {
		err = ErrorsNew(resp)
		return
	}
	util.JsonToType(resp, &at)
	return
}

// 获取用户信息
func GetWxSnsUserInfo(w WxAccessToken) (user WxUserInfo, err error) {
	h := util.HttpOk{Url: fmt.Sprintf(URLWxSnsUserInfo, w.AccessToken, w.Openid)}
	err = h.Query()
	if err != nil {
		return
	}
	resp := h.ResponseBody
	if strings.Index(resp, "errcode") > 0 {
		err = ErrorsNew(resp)
		return
	}
	util.JsonToType(resp, &user)
	return
}

// 获取关注用户的信息
func GetWxCgiBinUserInfo(token, openId string) (user WxUserInfo, err error) {
	h := util.HttpOk{Url: fmt.Sprintf(URLWxCgiBinUserInfo, token, openId)}
	err = h.Query()
	if err != nil {
		return
	}
	resp := h.ResponseBody
	if strings.Index(resp, "errcode") > 0 {
		err = ErrorsNew(resp)
		return
	}
	util.JsonToType(resp, &user)
	return
}
func GetWxPcCgiBinUserInfo(token, openId string) (user WxUserInfo, err error) {
	h := util.HttpOk{Url: fmt.Sprintf(URLWxCgiBinUserInfo, token, openId)}
	err = h.Query()
	if err != nil {
		return
	}
	resp := h.ResponseBody
	if strings.Index(resp, "errcode") > 0 {
		err = ErrorsNew(resp)
		return
	}
	util.JsonToType(resp, &user)
	return
}

type WxMqMsgTemp struct {
	OpenId        string `json:"openId"`        // 接收信息用户的openID
	TempId        string `json:"tempId"`        // 模板ID
	Url           string `json:"url"`           // 跳转路径
	MinAppId      string `json:"minAppId"`      //小程序ID
	MinAppPath    string `json:"minAppPath"`    //小程序路径
	FirstValue    string `json:"firstValue"`    //标题值
	FirstColor    string `json:"firstColor"`    //标题颜色
	Keyword1Value string `json:"keyword1Value"` //第一个参数
	Keyword1Color string `json:"keyword1Color"` //第一个颜色
	Keyword2Value string `json:"keyword2Value"` //第二个参数
	Keyword2Color string `json:"keyword2Color"` //第二个颜色
	Keyword3Value string `json:"keyword3Value"` //第三个参数
	Keyword3Color string `json:"keyword3Color"` //第三个颜色
	RemarkValue   string `json:"remarkValue"`   // 最后值
	RemarkColor   string `json:"remarkColor"`   // 最后值颜色
}

/*
`{
           "touser":"OPENID",
           "template_id":"TEMPLATE_ID",
           "url":"URL",
           "miniprogram":{
             "appid":"MINI_APPID",
             "pagepath":"PAGEPATH"
           },
           "data":{
                   "first": {
                       "value":"FIRST_VALUE",
                       "color":"FIRST_COLOR"
                   },
                   "keyword1":{
                       "value":"KEYWORD1_VALUE",
                       "color":"KEYWORD1_COLOR"
                   },
                   "keyword2": {
                       "value":"KEYWORD2_VALUE",
                       "color":"KEYWORD2_COLOR"
                   },
                   "keyword3": {
                       "value":"KEYWORD3_VALUE",
                       "color":"KEYWORD3_COLOR"
                   },
                   "remark":{
                       "value":"REMARK_VALUE",
                       "color":"REMARK_COLOR"
                   }
           }
	}`

*/
// TODO 微信消息模板
func (w *WxToken) WxMqMessageTemplateSend(data WxMqMsgTemp) (v string, err error) {
	token, err := w.GetAccessTokenStr()
	if err != nil {
		return
	}
	URL := strings.ReplaceAll(URLWxMqMessageTemplateSend, "ACCESS_TOKEN", token)
	http := util.HttpOk{Url: URL, Method: `POST`}
	temp := `{
           "touser":"` + data.OpenId + `",
           "template_id":"` + data.TempId + `",
           "url":"` + data.Url + `",
           "miniprogram":{
             "appid":"` + data.MinAppId + `",
             "pagepath":"` + data.MinAppPath + `"
           },
           "data":{
                   "first": {
                       "value":"` + data.FirstValue + `",
                       "color":"` + data.FirstColor + `"
                   },
                   "keyword1":{
                       "value":"` + data.Keyword1Value + `",
                       "color":"` + data.Keyword1Color + `"
                   },
                   "keyword2": {
                       "value":"` + data.Keyword2Value + `",
                       "color":"` + data.Keyword2Color + `"
                   },
                   "keyword3": {
                       "value":"` + data.Keyword3Value + `",
                       "color":"` + data.Keyword3Color + `"
                   },
                   "remark":{
                       "value":"` + data.RemarkValue + `",
                       "color":"` + data.RemarkColor + `"
                   }
           }
	}`

	http.Params = temp
	fmt.Println("微信消息通知：")
	fmt.Println(util.JsonToStr(data))
	http.Query()
	v = http.ResponseBody
	fmt.Println(v)
	if strings.Index(v, `"errmsg":"ok"`) < 0 {
		err = ErrorsNew(v)
	}
	return
}

type WxShortUrl struct {
	ErrCode  int    `json:"errcode"`
	ErrMsg   string `json:"errmsg"`
	ShortUrl string `json:"short_url"`
}

func (w *WxToken) ShortUrl(langUrl string) (string, error) {
	ACCESS_TOKEN, err := w.GetAccessTokenStr()
	if err != nil {
		return "", err
	}
	uri := `https://api.weixin.qq.com/cgi-bin/shorturl?access_token=` + ACCESS_TOKEN
	http := util.HttpOk{Url: uri, Method: "POST"}
	http.QueryJsonByte([]byte(`{"action":"long2short","long_url":"` + langUrl + `"}`))
	v := http.ResponseBody
	var s WxShortUrl
	util.JsonToType(v, &s)
	if s.ErrMsg != "ok" {
		return "", ErrorsNew(v)
	}
	return s.ShortUrl, nil
}

func ErrorsNew(v string) error {
	return errors.New(v)
} //微信短连接生成
func (w *WxToken) DecryptToString(ciphertext, nonce, associatedData string) (string, error) {
	return utils.DecryptToString(w.ApiV3Key, associatedData, nonce, ciphertext)
} //解密

type WxAppUserInfo struct {
	Openid    string         `json:"openId"`
	Nickname  string         `json:"nickName"`
	Gender    int            `json:"gender"`
	Language  string         `json:"language"`
	City      string         `json:"city"`
	Province  string         `json:"province"`
	Country   string         `json:"country"`
	Avatarurl string         `json:"avatarUrl"`
	Watermark WxAppWatermark `json:"watermark"`
}
type WxAppWatermark struct {
	Timestamp int    `json:"timestamp"`
	Appid     string `json:"appid"`
}

type Refunds struct {
	//TransactionID string        `json:"transaction_id"` //原支付交易对应的微信订单号。
	OutTradeNo  string        `json:"out_trade_no"`  //原支付交易对应的商户订单号。
	OutRefundNo string        `json:"out_refund_no"` //商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。
	Reason      string        `json:"reason"`        //若商户传入，会在下发给用户的退款消息中体现退款原因。
	NotifyURL   string        `json:"notify_url"`    //异步接收微信支付退款结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。 如果参数中传了notify_url，则商户平台上配置的回调地址将不会生效，优先回调当前传的这个地址。
	Amount      RefundsAmount `json:"amount"`        //订单金额信息。
}
type RefundsAmount struct {
	Refund   int    `json:"refund"`   //退款金额，币种的最小单位，只能为整数，不能超过原订单支付金额。
	Total    int    `json:"total"`    //原支付交易的订单总金额，币种的最小单位，只能为整数。
	Currency string `json:"currency"` //符合ISO 4217标准的三位字母代码，目前只支持人民币：CNY。
} //退款

type WeChatPayResult struct {
	ID           string                  `json:"id"`
	CreateTime   time.Time               `json:"create_time"`
	ResourceType string                  `json:"resource_type"`
	EventType    string                  `json:"event_type"`
	Summary      string                  `json:"summary"`
	Resource     WeChatPayResultResource `json:"resource"`
}
type WeChatPayResultResource struct {
	OriginalType   string `json:"original_type"`
	Algorithm      string `json:"algorithm"`
	Ciphertext     string `json:"ciphertext"`
	AssociatedData string `json:"associated_data"`
	Nonce          string `json:"nonce"`
}

type WeChatPayResultData struct {
	Mchid          string                    `json:"mchid"`
	Appid          string                    `json:"appid"`
	OutTradeNo     string                    `json:"out_trade_no"`
	TransactionID  string                    `json:"transaction_id"`
	TradeType      string                    `json:"trade_type"`
	TradeState     string                    `json:"trade_state"`
	TradeStateDesc string                    `json:"trade_state_desc"`
	BankType       string                    `json:"bank_type"`
	Attach         string                    `json:"attach"`
	SuccessTime    time.Time                 `json:"success_time"`
	Total          int                       `json:"total"`
	PayerTotal     int                       `json:"payer_total"`
	Openid         string                    `json:"openid"`
	Payer          WeChatPayResultDataPayer  `json:"payer"`
	Amount         WeChatPayResultDataAmount `json:"amount"`
}
type WeChatPayResultDataPayer struct {
	Openid string `json:"openid"`
}
type WeChatPayResultDataAmount struct {
	Total         int    `json:"total"`
	PayerTotal    int    `json:"payer_total"`
	Currency      string `json:"currency"`
	PayerCurrency string `json:"payer_currency"`
}

func (w *WeChatPayResultData) Init() {
	w.Openid = w.Payer.Openid
	w.Total = w.Amount.Total
	w.PayerTotal = w.Amount.PayerTotal
}

type Autogenerated struct {
	Amount struct {
		Currency         string `json:"currency"`
		DiscountRefund   int    `json:"discount_refund"`
		PayerRefund      int    `json:"payer_refund"`
		PayerTotal       int    `json:"payer_total"`
		Refund           int    `json:"refund"`
		SettlementRefund int    `json:"settlement_refund"`
		SettlementTotal  int    `json:"settlement_total"`
		Total            int    `json:"total"`
	} `json:"amount"`
	Channel             string        `json:"channel"`
	CreateTime          time.Time     `json:"create_time"`
	FundsAccount        string        `json:"funds_account"`
	OutRefundNo         string        `json:"out_refund_no"`
	OutTradeNo          string        `json:"out_trade_no"`
	PromotionDetail     []interface{} `json:"promotion_detail"`
	RefundID            string        `json:"refund_id"`
	Status              string        `json:"status"`
	TransactionID       string        `json:"transaction_id"`
	UserReceivedAccount string        `json:"user_received_account"`
}
